package org.oc.qaq.service.Impl;

import jakarta.servlet.http.HttpServletRequest;
import org.oc.qaq.constant.MessageConstant;
import org.oc.qaq.constant.StatusConstant;
import org.oc.qaq.context.UserContext;
import org.oc.qaq.dto.*;
import org.oc.qaq.entity.Role;
import org.oc.qaq.entity.User;
import org.oc.qaq.entity.UserRole;
import org.oc.qaq.exception.AccountNotAvailableException;
import org.oc.qaq.exception.BaseException;
import org.oc.qaq.exception.UserAlreadyExistException;
import org.oc.qaq.exception.UserNotFoundException;
import org.oc.qaq.mapper.RoleMapper;
import org.oc.qaq.mapper.UserMapper;
import org.oc.qaq.result.Result;
import org.oc.qaq.service.UserService;
import org.oc.qaq.utils.AliOssUtil;
import org.oc.qaq.utils.JwtUtil;
import org.oc.qaq.vo.UserVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    private AliOssUtil aliOssUtil;

    @Override
    public ResponseEntity<?> login(UserLoginDTO userLoginDTO) {
        User user = userMapper.selectOneByUsername(userLoginDTO.getUsername());

        if (user == null) {
            throw new UserNotFoundException(MessageConstant.LOGIN_ERROR);
        }

        if (Objects.equals(user.getStatus(), StatusConstant.DISABLE)) {
            throw new AccountNotAvailableException(MessageConstant.ACCOUNT_LOCKED);
        }
        try {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            userLoginDTO.getUsername(), userLoginDTO.getPassword()
                    )
            );

            SecurityContextHolder.getContext().setAuthentication(authentication);
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            String token = jwtUtil.generateToken(userDetails);

            // Redis 保存 token
            String redisKey = "LOGIN_TOKEN:" + user.getUsername();
            redisTemplate.opsForValue().set(redisKey, token, 3, TimeUnit.HOURS);

            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();

            UserVO userVO = UserVO.builder()
                    .id(user.getId())
                    .username(userLoginDTO.getUsername())
                    .email(user.getEmail())
                    .avatar(user.getAvatar())
                    .token(token)
                    .roles(authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
                    .build();

            return ResponseEntity.ok(Result.success(userVO));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
                        "code", 401,
                        "message", MessageConstant.LOGIN_ERROR
                ));
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
                    "code", 500,
                    "message", MessageConstant.SYSTEM_ERROR
            ));
        }
    }

    @Override
    public Result<String> register(UserRegisterDTO userRegisterDTO) {
        if (userMapper.selectOneByUsername(userRegisterDTO.getUsername()) != null) {
            throw new UserAlreadyExistException(MessageConstant.ALREADY_EXIST);
        }

        User user = new User();
        String id = UUID.randomUUID().toString();
        user.setId(id);
        user.setUsername(userRegisterDTO.getUsername());
        user.setPassword(passwordEncoder.encode(userRegisterDTO.getPassword()));
        user.setEmail(userRegisterDTO.getEmail());
        user.setStatus(StatusConstant.ENABLE);
        userMapper.insert(user);

        List<String> roles = new ArrayList<>();
        roles.add("USER");
        List<UserRole> userRoles = getUserRoles(id, roles);
        roleMapper.batchInsertUserRole(userRoles);

        return Result.success();
    }

    @Override
    public Result<?> logout(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            String username = jwtUtil.extractClaims(token).getSubject();
            redisTemplate.delete("LOGIN_TOKEN:" + username);
        }
        return Result.success();
    }

    @Override
    public void add(UserDTO userDTO) {
        User user = userMapper.selectOneByUsername(userDTO.getUsername());
        if (user == null) {
            user = new User();
            BeanUtils.copyProperties(userDTO, user);
            
            user.setId(UUID.randomUUID().toString());
            user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
            user.setStatus(StatusConstant.ENABLE);
            userMapper.insert(user);

            List<UserRole> userRoles = getUserRoles(user.getId(), userDTO.getRoles());
            roleMapper.batchInsertUserRole(userRoles);
        } else {
            throw new UserAlreadyExistException(MessageConstant.ALREADY_EXIST);
        }
    }

    @Override
    @Transactional
    public void update(UserDTO userDTO) {
        User user = userMapper.selectOneById(userDTO.getId());
        if (user == null) {
            throw new UserNotFoundException("用户不存在");
        }
        BeanUtils.copyProperties(userDTO, user);
        if (user.getPassword() != null) {
            user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        }
        userMapper.update(user);

        roleMapper.deleteByUserId(userDTO.getId());

        Set<Role> roles = userDTO.getRoles();
        List<UserRole> newUserRoles = getUserRoles(userDTO.getId(), roles);
        roleMapper.batchInsertUserRole(newUserRoles);
    }

    @Override
    @Transactional
    public void deleteBatch(List<String> uids) {
        uids.forEach(uid -> {
            User user = userMapper.selectOneById(uid);
            if (user == null) {
                throw new UserNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
            }
            roleMapper.deleteByUserId(uid);
            userMapper.delete(uid);
        });
    }

    @Override
    public void updateStatus(String uid, int status) {
        User user = User.builder()
                .id(uid)
                .status(status)
                .build();
        userMapper.update(user);
    }

    @Override
    public Result<List<UserDTO>> list(UserPageQueryDTO queryDTO) {
        List<UserDTO> users = userMapper.list(queryDTO);
        users.forEach(user -> {
            Set<Role> roles = userMapper.findRolesByUsername(user.getUsername());
            user.setRoles(roles);
        });

        return Result.success(users);
    }

    @Override
    public void delete(String uid) {
        User user = userMapper.selectOneById(uid);
        if (user == null) {
            throw new UserNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        roleMapper.deleteByUserId(uid);

        userMapper.delete(uid);
    }

    @Override
    public Result<?> uploadAvatar(String id, MultipartFile file) throws IOException {
        if (userMapper.selectOneById(id) == null) {
            throw new UserNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        if (file == null) {
            throw new BaseException("头像文件为空");
        }
        String[] split = file.getOriginalFilename().split("\\.");
        String path ="avatar/" + id + "." + split[split.length - 1];
        String upload_url = aliOssUtil.upload(file.getBytes(), path);
        User one = new User();
        one.setId(id);
        one.setAvatar(upload_url);
        userMapper.update(one);
        return Result.success(upload_url);
    }

    @Override
    public Result<?> me() {
        String id = UserContext.getCurrentId();
        User user = userMapper.selectOneById(id);
        UserVO userVO = UserVO.builder().build();
        BeanUtils.copyProperties(user, userVO);
        return Result.success(userVO);
    }

    @Override
    public void updateMe(UserDTO userDTO) {
        String id = UserContext.getCurrentId();
        userDTO.setId(id);
        if (userDTO.getPassword() != null) {
            userDTO.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        }
        if (!userDTO.getUsername().isBlank()) {
            User user = userMapper.selectOneByUsername(userDTO.getUsername());
            if (user != null) {
                throw new UserAlreadyExistException(MessageConstant.ALREADY_EXIST);
            }
        }
        User one = new User();
        BeanUtils.copyProperties(userDTO, one);
        userMapper.update(one);

        // 清除Redis中Token
        String username = userMapper.selectOneById(id).getUsername();
        redisTemplate.delete("LOGIN_TOKEN:" + username);
    }

    @Override
    public void changePassword(PasswordDTO passwordDTO) {
        String userId = UserContext.getCurrentId();
        User user = userMapper.selectOneById(userId);
        if (passwordEncoder.matches(passwordDTO.getOldPassword(), user.getPassword())) {
            user.setPassword(passwordEncoder.encode(passwordDTO.getNewPassword()));
            userMapper.update(user);
        } else {
            throw new BaseException("旧密码错误");
        }
    }


    private List<UserRole> getUserRoles(String uid, Set<Role> roles) {
        List<Long> rolesIds = new ArrayList<>();
        roles.forEach(role -> {
            rolesIds.add(role.getId());
        });
        return rolesIds.stream().
                map(id -> new UserRole(uid, id))
                .toList();
    }

    private List<UserRole> getUserRoles(String uid, List<String> roles) {
        List<Long> rolesIds = new ArrayList<>();
        roles.forEach(role -> {
            Long id = roleMapper.findRoleIdByName(role);
            rolesIds.add(id);
        });
        return rolesIds.stream().
                map(id -> new UserRole(uid, id))
                .toList();
    }
}
